﻿using LJ.Abstractions.Config;
using LJ.Abstractions.Extensions;
using LJ.WebApi.Abstractions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace LJ.WebApi.Mvc.Internal;


public class WebApiServiceConvention(IConfigProvider configProvider, IWebApiHandlerManager webApiHandlerManager) : IWebApiServiceConvention
{

    private readonly WebApiConfig _webApiConfig = configProvider.Get<WebApiConfig>();
    private readonly IWebApiHandlerManager _webApiHandlerManager = webApiHandlerManager;

    public void Apply(ApplicationModel application)
    {

        var controllers = application.Controllers.Where(u => _webApiHandlerManager.IsApiController(u.ControllerType));

        foreach (var controller in controllers)
        {
            var webApiHandlerDescriptor = _webApiHandlerManager.GetWebApiHandlerDescriptor(controller.ControllerType);

            ConfigureDynamicWebApi(controller, webApiHandlerDescriptor);
        }
    }

    #region ConfigureDynamicWebApi
    private void ConfigureDynamicWebApi(ControllerModel controller, WebApiHandlerDescriptor webApiHandlerDescriptor)
    {
        ConfigureController(controller, webApiHandlerDescriptor);
        ConfigureApiExplorer(controller, webApiHandlerDescriptor);
        ConfigureSelector(controller, webApiHandlerDescriptor);
        ConfigureParameters(controller, webApiHandlerDescriptor);

    }



    #endregion

    #region ConfigureController
    private static void ConfigureController(ControllerModel controller, WebApiHandlerDescriptor webApiHandlerDescriptor)
    {
        controller.ControllerName = webApiHandlerDescriptor.Controller;
    }
    #endregion

    #region ConfigureSelector
    private void ConfigureSelector(ControllerModel controller, WebApiHandlerDescriptor webApiHandlerDescriptor)
    {
        if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
        {
            return;
        }

        foreach (var action in controller.Actions)
        {
            ConfigureSelector(action, webApiHandlerDescriptor);
        }
    }
    private void ConfigureSelector(ActionModel action, WebApiHandlerDescriptor webApiHandlerDescriptor)
    {
        if (action.ActionMethod.Name != "ExecuteAsync")
        {
            return;
        }

        action.ActionName = webApiHandlerDescriptor.Action;

        if (action.Selectors == null || action.Selectors.Any(a => a.ActionConstraints == null || a.ActionConstraints.Count == 0))
        {
            AddAppServiceSelector(action, webApiHandlerDescriptor);
        }
        else
        {
            NormalizeSelectorRoutes(action, webApiHandlerDescriptor);
        }
    }
    private void AddAppServiceSelector(ActionModel action, WebApiHandlerDescriptor webApiHandlerDescriptor)
    {

        var appServiceSelectorModel = action.Selectors[0];

        appServiceSelectorModel.AttributeRouteModel ??= CreateActionRouteModel(webApiHandlerDescriptor);

        if (!appServiceSelectorModel.ActionConstraints.Any())
        {

            switch (webApiHandlerDescriptor.Type)
            {
                case WebApiType.Get:
                    appServiceSelectorModel.EndpointMetadata.Add(new HttpGetAttribute());
                    appServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(["GET"]));
                    break;
                case WebApiType.Post:
                    appServiceSelectorModel.EndpointMetadata.Add(new HttpPostAttribute());
                    appServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(["POST"]));
                    break;
                case WebApiType.Put:
                    appServiceSelectorModel.EndpointMetadata.Add(new HttpPutAttribute());
                    appServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(["PUT"]));
                    break;
                case WebApiType.Delete:
                    appServiceSelectorModel.EndpointMetadata.Add(new HttpDeleteAttribute());
                    appServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(["DELETE"]));
                    break;
                default:
                    throw new ArgumentException($"Unsupported http verb: {webApiHandlerDescriptor.Type}.");
            }
        }
    }


    private void NormalizeSelectorRoutes(ActionModel action, WebApiHandlerDescriptor webApiHandlerDescriptor)
    {
        foreach (var selector in action.Selectors)
        {
            selector.AttributeRouteModel = selector.AttributeRouteModel == null ?
                 CreateActionRouteModel(webApiHandlerDescriptor) :
                 AttributeRouteModel.CombineAttributeRouteModel(CreateActionRouteModel(webApiHandlerDescriptor), selector.AttributeRouteModel);
        }
    }
    #endregion

    #region ConfigureApiExplorer
    private static void ConfigureApiExplorer(ControllerModel controller, WebApiHandlerDescriptor webApiHandlerDescriptor)
    {
        if (string.IsNullOrEmpty(controller.ApiExplorer.GroupName))
        {
            controller.ApiExplorer.GroupName = webApiHandlerDescriptor.Controller;
        }

        if (controller.ApiExplorer.IsVisible == null)
        {
            controller.ApiExplorer.IsVisible = true;
        }

        foreach (var action in controller.Actions)
        {
            if (action.ActionMethod.Name == "ExecuteAsync")
            {
                if (action.ApiExplorer.IsVisible == null)
                {
                    action.ApiExplorer.IsVisible = true;
                }
            }
            else
            {
                action.ApiExplorer.IsVisible = false;
            }
        }
    }

    #endregion


    #region ConfigureParameters
    private void ConfigureParameters(ControllerModel controller, WebApiHandlerDescriptor webApiHandlerDescriptor)
    {
        foreach (var action in controller.Actions)
        {
            foreach (var parameter in action.Parameters)
            {
                if (parameter.BindingInfo != null)
                {
                    continue;
                }

                if (parameter.ParameterType.IsPrimitiveExtendedIncludingNullable())
                {
                    continue;
                }

                if (parameter.ParameterType == webApiHandlerDescriptor.RouteType)
                {
                    parameter.BindingInfo = BindingInfo.GetBindingInfo([new FromRouteAttribute() { Name= "route" }]);
                }


                if (CanUseFormBodyBinding(action))
                {
                    if (parameter.ParameterType == webApiHandlerDescriptor.RequestType)
                    {
                        parameter.BindingInfo = BindingInfo.GetBindingInfo([new FromBodyAttribute()]);
                    }
                }
            }


            // 获取真实类型
            var returnType = action.ActionMethod.GetRealReturnType();
            if (returnType == typeof(void) || returnType == typeof(Task))
            {
                returnType = typeof(object);
            }

            //if () return;
            // 添加规范化结果特性
            action.Filters.Add(new ProducesResponseTypeAttribute(returnType, StatusCodes.Status200OK));
        }
    }

    #endregion

    #region private
    /// <summary>
    /// 是否可以使用Body传参
    /// </summary>
    /// <param name="action"></param>
    /// <returns></returns>
    private static bool CanUseFormBodyBinding(ActionModel action)
    {
        var httpMethods = action.Selectors.SelectMany(selector => selector.ActionConstraints)
            .OfType<HttpMethodActionConstraint>().SelectMany(temp => temp.HttpMethods).ToList();

        if (httpMethods.Contains("GET") || httpMethods.Contains("TRACE") || httpMethods.Contains("HEAD"))
            return false;

        return true;
    }

    private AttributeRouteModel CreateActionRouteModel(WebApiHandlerDescriptor webApiHandlerDescriptor)
    {
        var route = $"{_webApiConfig.ApiPrefix}/{webApiHandlerDescriptor.Controller}/{webApiHandlerDescriptor.Action}/{{route}}".Replace("//", "/");

        return new AttributeRouteModel(new RouteAttribute(route));
    }

    #endregion
}
